﻿/**
 * ProxyFactory, Proxy
 * This class is provided to create proxy objects following the configuration
 * @author ShanFan
 * @created 24-3-2014
 */

// Dependencies
var fs             = require('fs')
    , http         = require('http')
    , https        = require('https')
    , url          = require('url')
    , querystring  = require('querystring')
    , iconv        = require('iconv-lite')
    , BufferHelper = require('bufferhelper')
    , moment = require('moment');

var InterfacefManager = require('./interfacemanager');

var config       = require('../config');

// Instance of InterfaceManager, will be intialized when the proxy.use() is called.
var interfaceManager;

var STATUS_MOCK     = 'mock';
var STATUS_MOCK_ERR = 'mockerr';
var ENCODING_RAW    = 'raw';

// Current Proxy Status
// var CurrentStatus;

// Proxy constructor
function Proxy(options){
    this._opt  = options || {};
    this._urls = this._opt.urls || {};
    if(this._opt.status === STATUS_MOCK || this._opt.status === STATUS_MOCK_ERR){
        return;
    }
}

/**
 * use
 * @param {InterfaceManager} ifmgr
 * @throws errors
 */
Proxy.use = function(ifmgr){

    if(ifmgr instanceof InterfacefManager){
        interfaceManager = ifmgr;
    } else {
        throw new Error('Proxy can only use instance of InterfacefManager!');
    }

    this._engineName = interfaceManager.getEngine();

    return this;
};

Proxy.getMockEngine = function(){
    if(this._mockEngine){
        return this._mockEngine;
    }
    return this._mockEngine = require(this._engineName);
};

Proxy.getInterfaceIdsByPrefix = function(pattern){
    return interfaceManager.getInterfaceIdsByPrefix(pattern);
};

// @throws errors
Proxy.getRule = function(interfaceId){
    return interfaceManager.getRule(interfaceId);
};

// {Object} An object map to store created proxies. The key is interface id
// and the value is the proxy instance. 
Proxy.objects = {};

// Proxy factory
// @throws errors
Proxy.create = function(interfaceId){
    if(!!this.objects[interfaceId]){
        return this.objects[interfaceId];
    }
    var opt = interfaceManager.getProfile(interfaceId);
    //console.log(opt);
    if(!opt){
        throw new Error('Invalid interface id: ' + interfaceId);
    }
    return this.objects[interfaceId] = new this(opt);
};

Proxy.prototype = {
    request          : function(params, callback, errCallback){
        // if ( typeof callback !== 'function' ) {
        //     console.error( 'No callback function for request = ', this._opt );
        //     return;
        // }
        if(this._opt.isCookieNeeded === true && cookie === undefined){
            throw new Error('This request is cookie needed, you must set a cookie for it before request. id = ' + this._opt.id);
        }

        errCallback = typeof errCallback !== 'function'
            ? function(e){
            console.error(e);
        }
            : errCallback;

        if(this._opt.status === STATUS_MOCK
            || this._opt.status === STATUS_MOCK_ERR){
            this._mockRequest(params, callback, errCallback);
            return;
        }
        var self     = this;
        var isNausea = self._opt.urls.isNausea; // 获取接口的变态类型 为了适应E号通那变态的接口
        var parameterFormat = self._opt.urls.parameterFormat; // 获取传递参数的所需格式格式 用于POST请求参数格式转换

        if(self._opt.options.method == 'POST' || self._opt.options.method == 'post'){
            var options = self._opt.options;

            if(isNausea == 'false'){ // 接口不变态的
                var postPayload = this._objExtend({}, self._opt.payload, params);
                if(parameterFormat == 'JSON'){
                    var target = JSON.stringify(postPayload);
                }
                if(parameterFormat == 'querystring'){
                    var target = querystring.stringify(postPayload);
                }
                if(parameterFormat == 'unformatted'){
                    // var target = postPayload;
                    var target = this._queryStringify(postPayload);
                }
            } else { // E号通变态接口的 （不要问我为什么，我不想说）
                var postPayload = this._objExtend({}, self._opt.payload, params);
                var target      = this._isNauseaFormat(postPayload);
            }
        }
        if(self._opt.options.method == 'GET' || self._opt.options.method == 'get'){
            var getPayload = this._objExtend({}, self._opt.payload, params);

            if(isNausea == 'false'){ // 接口不变态的
                if(parameterFormat == 'JSON'){
                    var param = JSON.stringify(getPayload);
                }
                if(parameterFormat == 'querystring'){
                    var param = querystring.stringify(getPayload);
                }
                if(parameterFormat == 'unformatted'){
                    var param = this._queryStringify(getPayload);
                }
            } else { // E号通变态接口的 （不要问我为什么，我不想说）
                var param = this._isNauseaFormat(getPayload);
            }

            var options = {
                host    : self._opt.options.host,
                port    : self._opt.options.port,
                path    : self._opt.options.path + '?' + param,
                method  : self._opt.options.method,
                headers : self._opt.options.headers
            }
        }

        var postPayload = this._objExtend({},self._opt.payload,params);
        // http 类型请求
        if(self._opt.pactType == 'http'){
            var req = http.request(options, function(res){
                var timer = setTimeout(function(){
                    errCallback(new Error('timeout'));
                }, self._opt.timeout || 5000);

                var bufferHelper = new BufferHelper();

                var _req_chunk = '';

                res.on('data', function(chunk){
                    bufferHelper.concat( chunk );
                    _req_chunk += chunk;

                    var logStr = '[' + moment().format('YYYY-MM-DD HH:mm:ss.S') + '] '
                                 + '\n\t请求方法：' + self._opt.options.method
                                 + '\n\t请求地址：'
                                 + self._opt.pactType + '://' + self._opt.options.host + ':' + self._opt.options.port + self._opt.options.path
                                 + '\n\t请求参数：' 
                                 + JSON.stringify(postPayload);

                    if(!config.debug){
                        console.log(logStr);

                        if(res.statusCode >= 400){
                            console.error(new Error('[注意]接口错误，状态码：' + res.statusCode + ', 返回数据：' + _req_chunk));
                        }else{
                            console.log('\t接口正常');
                        }
                    }
                });

                res.on('error', function(err){
                    console.error(new Error('[注意]接口错误，状态码：' + res.statusCode));
                });

                res.on('end', function(){
                    var buffer = bufferHelper.toBuffer();
                    try {
                        var result = self._opt.dataType !== 'json'
                            ? iconv.decode( buffer, 'utf-8' )
                            : iconv.decode( buffer, 'utf-8' );
                    } catch ( e ) {
                        clearTimeout( timer );
                        errCallback( new Error( "The result has syntax error. " + e ) );
                        return;
                    }
                    clearTimeout( timer );
                    //callback(_req_chunk);
                    callback(result);
                });

            });
        }

        // https 类型请求
        if(self._opt.pactType == 'https'){
            var req = https.request(options, function(res){
                var timer = setTimeout(function(){
                    errCallback(new Error('timeout'));
                }, self._opt.timeout || 5000);

                var bufferHelper = new BufferHelper();
                var _req_chunk = '';

                res.on('data', function(chunk){
                    bufferHelper.concat( chunk );
                    _req_chunk += chunk;

                    var logStr = '[' + moment().format('YYYY-MM-DD HH:mm:ss.S') + '] '
                                 + '\n\t请求方法：' + self._opt.options.method
                                 + '\n\t请求地址：'
                                 + self._opt.pactType + '://' + self._opt.options.host + ':' + self._opt.options.port + self._opt.options.path
                                 + '\n\t请求参数：' 
                                 + JSON.stringify(postPayload);

                    if(!config.debug){
                        console.log(logStr);

                        if(res.statusCode >= 400){
                            console.error(new Error('[注意]接口错误，状态码：' + res.statusCode + ', 返回数据：' + _req_chunk));
                        }else{
                            console.log('\t接口正常');
                        }
                    }
                });

                res.on('error', function(err){
                    console.error(new Error('[注意]接口错误，状态码：' + res.statusCode));
                });

                res.on('end', function(){
                    var buffer = bufferHelper.toBuffer();
                    try {
                        var result = self._opt.dataType !== 'json'
                            ? iconv.decode( buffer, 'utf-8' )
                            : iconv.decode( buffer, 'utf-8' );
                    } catch ( e ) {
                        clearTimeout( timer );
                        errCallback( new Error( "The result has syntax error. " + e ) );
                        return;
                    }
                    clearTimeout( timer );
                    //callback(_req_chunk);
                    callback(result);
                });

            });
        }

        self._opt.options.method !== 'POST' || req.write(target);

        req.on('error', function(e){
            errCallback(e);
        });

        req.end();
    },
    getOption        : function(name){
        return this._opt[name];
    },
    _queryStringify  : function(params){
        if(!params || typeof params === 'string'){
            return params || '';
        } else if(params instanceof Array){
            return params.join('&');
        }
        var qs = [], val;
        for(var i in params){
            val = typeof params[i] === 'object'
                ? JSON.stringify(params[i])
                : params[i];
            // qs.push( i + '=' + val );
            qs.push(i + '=' + encodeURIComponent(val));
        }
        return qs.join('&');
    },
    _mockRequest     : function(params, callback, errCallback){
        try{
            var engine = Proxy.getMockEngine();
            if(!this._rule){
                this._rule = Proxy.getRule(this._opt.id);
            }
            if(this._opt.isRuleStatic){
                callback(this._opt.status === STATUS_MOCK
                    ? this._rule.response
                    : this._rule.responseError);
                return;
            }

            // special code for river-mock
            if(Proxy._engineName === 'river-mock'){
                callback(engine.spec2mock(this._rule));
                return;
            }
            // special code for mockjs
            callback(this._opt.status === STATUS_MOCK
                ? engine.mock(this._rule.response)
                : engine.mock(this._rule.responseError)
            );
        } catch (e) {
            errCallback(e);
        }
    },
    interceptRequest : function(req, res){
        if(this._opt.status === STATUS_MOCK
            || this._opt.status === STATUS_MOCK_ERR){
            this._mockRequest({}, function(data){
                res.end(typeof data === 'string' ? data : JSON.stringify(data));
            }, function(e){
                // console.error( 'Error ocurred when mocking data', e );
                res.statusCode = 500;
                res.end('Error orccured when mocking data');
            });
            return;
        }
        var self    = this;
        var options = {
            hostname : self._opt.hostname,
            port     : self._opt.port,
            path     : self._opt.path + '?' + req.url.replace(/^[^\?]*\?/, ''),
            method   : self._opt.method,
            headers  : req.headers
        };

        options.headers.host = self._opt.hostname;
        // delete options.headers.referer;
        // delete options.headers['x-requested-with'];
        // delete options.headers['connection'];
        // delete options.headers['accept'];
        delete options.headers['accept-encoding'];

        var req2 = http.request(options, function(res2){
            var bufferHelper = new BufferHelper();

            res2.on('data', function(chunk){
                bufferHelper.concat(chunk);
            });
            res2.on('end', function(){
                var buffer = bufferHelper.toBuffer();
                var result;
                try{
                    result = self._opt.encoding === ENCODING_RAW
                        ? buffer
                        : iconv.fromEncoding(buffer, self._opt.encoding);

                } catch (e) {
                    res.statusCode = 500;
                    res.end(e + '');
                    return;
                }
                res.setHeader('Set-Cookie', res2.headers['set-cookie']);
                res.setHeader('Content-Type'
                    , ( self._opt.dataType === 'json' ? 'application/json' : 'text/html' )
                    + ';charset=UTF-8');
                res.end(result);
            });
        });

        req2.on('error', function(e){
            res.statusCode = 500;
            res.end(e + '');
        });
        req.on('data', function(chunck){
            req2.write(chunck);
        });
        req.on('end', function(){
            req2.end();
        });

    },
    _objExtend       : function(targetObj){
        var target = targetObj || {},
            objArr = arguments;
        for(var i = 1; i < objArr.length; i++){
            for(var v in objArr[i]){
                target[v] = objArr[i][v];
            }
        }
        return target;
    },
    _isNauseaFormat  : function(params){ //处理E号通那变态的接口参数格式
        /*
         * 把这样的格式转化
         * queryParam:{
         *   userCode: "u0001",
         *   serviceId:"10018",
         *   serviceType:"1",
         *   queryParamJson:{
         *     msgCode:"c217089ce49c40f9b70f6e1a2d6eca7a"
         *   }
         * }
         * 转化为以下的格式
         * queryParam={"userCode":"u0001","serviceId":"10018","serviceType":"1","queryParamJson":"{\"msgCode\":\"c217089ce49c40f9b70f6e1a2d6eca7a\"}"}
         * E号通的就是这么变态
         * */
        var qs = {}, val;
        for(var i in params){ // 循环对象
            if(typeof params[i] == 'string'){ // 第一层参数如果是 string 直接赋值
                qs[i] = params[i];
            } else if(typeof params[i] == 'object'){ // 第一层参数如果是 object 进行循环
                for(var j in params[i]){
                    if(typeof params[i][j] == 'string'){ // 第二层参数如果是 string 直接赋值
                        qs[j] = params[i][j];
                    } else if(typeof params[i][j] == 'object'){ // 第二层参数如果是 object 进行JSON 化
                        val   = JSON.stringify(params[i][j]);
                        qs[j] = val;
                    }
                }
            }
        }
        /*
         * querystring.stringify(params) 将对象转换成字符串
         * JSON.stringify(qs) 将上面格式化的数据 再进行JSON格式化
         * 最终组合输出
         * */
        return querystring.stringify(params) + JSON.stringify(qs);
    }
};

var ProxyFactory = Proxy;

ProxyFactory.Interceptor = function(req, res){
    var interfaceId = req.url.split(/\?|\//)[1];
    if(interfaceId === '$interfaces'){
        var interfaces = interfaceManager.getClientInterfaces();
        res.end(JSON.stringify(interfaces));
        return;
    }

    try{
        proxy = this.create(interfaceId);
        if(proxy.getOption('intercepted') === false){
            throw new Error('This url is not intercepted by proxy.');
        }
    } catch (e) {
        res.statusCode = 404;
        res.end('Invalid url: ' + req.url + '\n' + e);
        return;
    }
    proxy.interceptRequest(req, res);
};

module.exports = ProxyFactory;

